package com.moons.uvcdemo;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.PixelFormat;
import android.graphics.PorterDuff;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.util.Log;
import android.util.Size;
import android.util.SparseArray;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.widget.FrameLayout;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import com.xqd.sdk.libaicamera.face.FaceObject;

import java.util.List;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.TimeUnit;

public class PreviewWidget extends FrameLayout {
    private static final String TAG  = "PreviewWidget";
    private SurfaceView mFrameSurfaceView;
    private SurfaceView mFaceSurfaceView;

    private BlockingQueue<byte[]> mFrameDataQueue;
    private BlockingQueue<List<FaceObject>> mFaceObjectQueue;
    private BlockingQueue<Bitmap> mBitmapFrameQueue;

    private DecodeThread mDecodeThread;
    private DrawFaceThread mDrawFaceThread;
    private DrawFrameThread mDrawFrameThread;
    private SparseArray<Rect> mCacheFaceBoxes;
    private Size mFrameSize;

    public PreviewWidget(@NonNull Context context) {
        super(context);
        init(context);

    }

    public PreviewWidget(@NonNull Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        init(context);
    }

    public PreviewWidget(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(context);
    }

    public void setFrameSize(Size size) {
        mFrameSize = size;
    }

    public boolean isFrameSizeAvailable() {
        return (mFrameSize!=null);
    }

    public void putFrameData(byte[] buffer) {
        if (!mFrameDataQueue.offer(buffer)) {
            Log.e(TAG, "mFrameDataQueue seems full.");
            mFrameDataQueue.poll();
            mFrameDataQueue.offer(buffer);
        }
    }

    public void putFaceObject(List<FaceObject> faceObjects) {
        if (!mFaceObjectQueue.offer(faceObjects)) {
            Log.e(TAG, "mFaceDataQueue seems full.");
            mFaceObjectQueue.poll();
            mFaceObjectQueue.offer(faceObjects);
        }
    }

    private void init(Context context) {
        initViews(context);
        initQueues();
        initThreads();
        mCacheFaceBoxes = new SparseArray<>();
        mCacheFaceBoxes.clear();
    }


    private void initViews(Context context) {
        mFrameSurfaceView = new SurfaceView(context);
        mFrameSurfaceView.getHolder().addCallback(new SurfaceHolder.Callback() {
            @Override
            public void surfaceCreated(SurfaceHolder surfaceHolder) {
                surfaceHolder.setFormat(PixelFormat.RGBA_8888);
            }

            @Override
            public void surfaceChanged(SurfaceHolder surfaceHolder, int i, int i1, int i2) {

            }

            @Override
            public void surfaceDestroyed(SurfaceHolder surfaceHolder) {

            }
        });
        mFaceSurfaceView = new SurfaceView(context);
        mFaceSurfaceView.getHolder().addCallback(new SurfaceHolder.Callback() {
            @Override
            public void surfaceCreated(SurfaceHolder surfaceHolder) {
                surfaceHolder.setFormat(PixelFormat.TRANSLUCENT);
                mFaceSurfaceView.setZOrderMediaOverlay(true);
            }

            @Override
            public void surfaceChanged(SurfaceHolder surfaceHolder, int i, int i1, int i2) {

            }

            @Override
            public void surfaceDestroyed(SurfaceHolder surfaceHolder) {

            }
        });
        addView(mFrameSurfaceView);
        addView(mFaceSurfaceView);

    }


    private void initQueues() {
        mFrameDataQueue = new ArrayBlockingQueue<>(2);
        mFaceObjectQueue = new ArrayBlockingQueue<>(2);
        mBitmapFrameQueue = new ArrayBlockingQueue<>(2);
        mFrameDataQueue.clear();
        mFaceObjectQueue.clear();
        mBitmapFrameQueue.clear();
    }

    private void initThreads() {
        mDecodeThread = new DecodeThread();
        mDrawFaceThread = new DrawFaceThread();
        mDrawFrameThread =new DrawFrameThread();
        mDecodeThread.start();
        mDrawFaceThread.start();
        mDrawFrameThread.start();
    }

    private class DrawFrameThread extends Thread {

        private boolean mRunning;
        public DrawFrameThread() {
            mRunning = false;
        }

        @Override
        public synchronized void start() {
            mRunning = true;
            super.start();
        }

        @Override
        public void run() {
            while(mRunning) {
                try {
                    Bitmap bitmap = mBitmapFrameQueue.poll(5, TimeUnit.MILLISECONDS);
                    if (bitmap != null) {
                        drawFrame(bitmap);
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }

        @Override
        public void interrupt() {
            mRunning = false;
            super.interrupt();
        }
    }

    private class DrawFaceThread extends Thread {

        private boolean mRunning;
        public DrawFaceThread() {
            mRunning = false;
        }

        @Override
        public synchronized void start() {
            mRunning = true;
            super.start();
        }

        @Override
        public void run() {
            while(mRunning) {
                try {
                    List<FaceObject> faceObjects = mFaceObjectQueue.poll(5, TimeUnit.MILLISECONDS);
                    if (faceObjects != null) {
                        drawFaces(faceObjects);

                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }

        @Override
        public void interrupt() {
            mRunning = false;
            super.interrupt();
        }
    }

    private class DecodeThread extends Thread {

        private boolean mRunning;
        public DecodeThread() {
            mRunning = false;
        }

        @Override
        public synchronized void start() {
            mRunning = true;
            super.start();
        }

        @Override
        public void run() {
            while(mRunning) {
                try {
                    byte []frame = mFrameDataQueue.poll(5, TimeUnit.MILLISECONDS);
                    if (frame != null) {
                        Log.d(TAG, "decode start");
                        Bitmap bitmap = BitmapFactory.decodeByteArray(frame, 0, frame.length);
                        if (bitmap != null) {
                            Log.d(TAG, "decode end");
                            if (!mBitmapFrameQueue.offer(bitmap)) {
                                Log.e(TAG, "it seeems that mBitmapFrameQueue is full.");
                                mBitmapFrameQueue.poll();
                                mBitmapFrameQueue.offer(bitmap);
                            }
                        }
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }

        @Override
        public void interrupt() {
            mRunning = false;
            super.interrupt();
        }
    }

    private void drawFrame(final Bitmap bitmap) {
        Log.d(TAG, "DrawFrame start");
        Rect dest = new Rect(0, 0, mFrameSurfaceView.getWidth(), mFrameSurfaceView.getHeight());
        Paint paint = new Paint();
        paint.setAntiAlias(true);
        paint.setDither(false);
        Rect src = new Rect(0, 0, bitmap.getWidth(), bitmap.getHeight());
        Canvas canvas = mFrameSurfaceView.getHolder().lockCanvas();
        canvas.drawBitmap(bitmap, src, dest, paint);
        mFrameSurfaceView.getHolder().unlockCanvasAndPost(canvas);
        Log.d(TAG, "DrawFrame end");
        if (!bitmap.isRecycled()) {
            bitmap.recycle();
        }
    }

    private void drawFaces(final List<FaceObject> faceObjects) {
        int frameWidth = mFrameSize.getWidth();
        int frameHeight = mFrameSize.getHeight();
        if ((frameHeight==0)||(frameWidth==0)) {
            return;
        }
        Canvas canvas = mFaceSurfaceView.getHolder().lockCanvas();
        canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
        Paint paint = new Paint();

        int surfaceWidth = mFaceSurfaceView.getWidth();
        int surfaceHeight = mFaceSurfaceView.getHeight();
        for (FaceObject faceObject : faceObjects) {

            int faceID = faceObject.id();

            Rect rect = new Rect();
            rect.left = faceObject.rect().left * surfaceWidth / frameWidth;
            rect.right = faceObject.rect().right * surfaceWidth / frameWidth;
            rect.top = faceObject.rect().top * surfaceHeight / frameHeight;
            rect.bottom = faceObject.rect().bottom * surfaceHeight /frameHeight;
            drawFaceBox(canvas, paint, rect);
            drawFaceInfo(canvas, paint, rect, faceObject);
        }
        mFaceSurfaceView.getHolder().unlockCanvasAndPost(canvas);
    }

    private void drawFaceBox(Canvas canvas, Paint paint, Rect rect) {
        paint.reset();
        paint.setStrokeWidth(6);
        paint.setStyle(Paint.Style.STROKE);
        paint.setColor(Color.WHITE);
        paint.setAntiAlias(true);
        canvas.drawRect(rect, paint);
    }

    private void drawFaceInfo(Canvas canvas, Paint paint, Rect rect, FaceObject faceObject) {
        paint.reset();
        paint.setStyle(Paint.Style.FILL);
        paint.setColor(Color.RED);
        paint.setTextSize(20);

        canvas.drawText( String.valueOf(faceObject.id()), rect.left - 5, rect.top - 5, paint);
    }

}
